#include "U2DataStreamFileStream.h"

#include "U2LogManager.h"
#include "U2Exception.h"


U2EG_NAMESPACE_USING


U2DataStreamFileStream::U2DataStreamFileStream(std::ifstream* s, bool freeOnClose, Endian endian)
: U2DataStream(READ, endian)
, mInStream(s)
, mFStreamRO(s)
, mFStream(0)
, mFreeOnClose(freeOnClose)
{
    // calculate the size
    mInStream->seekg(0, std::ios_base::end);
    mSize = (size_t)mInStream->tellg();
    mInStream->seekg(0, std::ios_base::beg);
    determineAccess();
}
//-----------------------------------------------------------------------
U2DataStreamFileStream::U2DataStreamFileStream(const U2String& name, 
                                           std::ifstream* s, bool freeOnClose, Endian endian)
: U2DataStream(name, READ, endian)
, mInStream(s)
, mFStreamRO(s)
, mFStream(0)
, mFreeOnClose(freeOnClose)
{
    // calculate the size
    mInStream->seekg(0, std::ios_base::end);
    mSize = (size_t)mInStream->tellg();
    mInStream->seekg(0, std::ios_base::beg);
    determineAccess();
}
//-----------------------------------------------------------------------
U2DataStreamFileStream::U2DataStreamFileStream(const U2String& name, 
                                           std::ifstream* s, size_t inSize, bool freeOnClose, Endian endian)
: U2DataStream(name, READ, endian)
, mInStream(s)
, mFStreamRO(s)
, mFStream(0)
, mFreeOnClose(freeOnClose)
{
    // Size is passed in
    mSize = inSize;
    determineAccess();
}
//---------------------------------------------------------------------
U2DataStreamFileStream::U2DataStreamFileStream(std::fstream* s, bool freeOnClose, Endian endian)
: U2DataStream(READ | WRITE, endian)
, mInStream(s)
, mFStreamRO(0)
, mFStream(s)
, mFreeOnClose(freeOnClose)
{
    // writeable!
    // calculate the size
    mInStream->seekg(0, std::ios_base::end);
    mSize = (size_t)mInStream->tellg();
    mInStream->seekg(0, std::ios_base::beg);
    determineAccess();

}
//-----------------------------------------------------------------------
U2DataStreamFileStream::U2DataStreamFileStream(const U2String& name, 
                                           std::fstream* s, bool freeOnClose, Endian endian)
: U2DataStream(name, READ | WRITE, endian)
, mInStream(s)
, mFStreamRO(0)
, mFStream(s)
, mFreeOnClose(freeOnClose)
{
    // writeable!
    // calculate the size
    mInStream->seekg(0, std::ios_base::end);
    mSize = (size_t)mInStream->tellg();
    mInStream->seekg(0, std::ios_base::beg);
    determineAccess();
}
//-----------------------------------------------------------------------
U2DataStreamFileStream::U2DataStreamFileStream(const U2String& name, 
                                           std::fstream* s, size_t inSize, bool freeOnClose, Endian endian)
: U2DataStream(name, READ | WRITE, endian)
, mInStream(s)
, mFStreamRO(0)
, mFStream(s)
, mFreeOnClose(freeOnClose)
{
    // writeable!
    // Size is passed in
    mSize = inSize;
    determineAccess();
}
//---------------------------------------------------------------------
void U2DataStreamFileStream::determineAccess()
{
    mAccess = 0;
    if (mInStream)
        mAccess |= READ;
    if (mFStream)
        mAccess |= WRITE;
}
//-----------------------------------------------------------------------
U2DataStreamFileStream::~U2DataStreamFileStream()
{
    close();
}
//-----------------------------------------------------------------------
size_t U2DataStreamFileStream::read(void* buf, size_t count)
{
    mInStream->read(static_cast<char*>(buf), static_cast<std::streamsize>(count));
    return mInStream->gcount();
}
//-----------------------------------------------------------------------
size_t U2DataStreamFileStream::write(const void* buf, size_t count)
{
    size_t written = 0;
    if (isWriteable() && mFStream)
    {
        mFStream->write(static_cast<const char*>(buf), static_cast<std::streamsize>(count));
        written = count;
    }
    return written;
}
//-----------------------------------------------------------------------
size_t U2DataStreamFileStream::readLine(char* buf, size_t maxCount, 
                                      const U2String& delim)
{
    if (delim.empty())
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, "No delimiter provided",
            "U2DataStreamFileStream::readLine");
    }
    if (delim.size() > 1)
    {
        U2LogManager::getSingleton().logMessage(
            "WARNING: U2DataStreamFileStream::readLine - using only first delimeter");
    }
    // Deal with both Unix & Windows LFs
    bool trimCR = false;
    if (delim.at(0) == '\n') 
    {
        trimCR = true;
    }
    // maxCount + 1 since count excludes terminator in getline
    mInStream->getline(buf, static_cast<std::streamsize>(maxCount+1), delim.at(0));
    size_t ret = mInStream->gcount();
    // three options
    // 1) we had an eof before we read a whole line
    // 2) we ran out of buffer space
    // 3) we read a whole line - in this case the delim character is taken from the stream but not written in the buffer so the read data is of length ret-1 and thus ends at index ret-2
    // in all cases the buffer will be null terminated for us

    if (mInStream->eof()) 
    {
        // no problem
    }
    else if (mInStream->fail())
    {
        // Did we fail because of maxCount hit? No - no terminating character
        // in included in the count in this case
        if (ret == maxCount)
        {
            // clear failbit for next time 
            mInStream->clear();
        }
        else
        {
            U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR, 
                "Streaming error occurred", 
                "U2DataStreamFileStream::readLine");
        }
    }
    else 
    {
        // we need to adjust ret because we want to use it as a
        // pointer to the terminating null character and it is
        // currently the length of the data read from the stream
        // i.e. 1 more than the length of the data in the buffer and
        // hence 1 more than the _index_ of the NULL character
        --ret;
    }

    // trim off CR if we found CR/LF
    if (trimCR && buf[ret-1] == '\r')
    {
        --ret;
        buf[ret] = '\0';
    }
    return ret;
}
//-----------------------------------------------------------------------
void U2DataStreamFileStream::skip(long count)
{
#if defined(STLPORT)
    // Workaround for STLport issues: After reached eof of file stream,
    // it's seems the stream was putted in intermediate state, and will be
    // fail if try to repositioning relative to current position.
    // Note: tellg() fail in this case too.
    if (mInStream->eof())
    {
        mInStream->clear();
        // Use seek relative to either begin or end to bring the stream
        // back to normal state.
        mInStream->seekg(0, std::ios::end);
    }
#endif 		
    mInStream->clear(); //Clear fail status in case eof was set
    mInStream->seekg(static_cast<std::ifstream::pos_type>(count), std::ios::cur);
}
//-----------------------------------------------------------------------
void U2DataStreamFileStream::seek( size_t pos )
{
    mInStream->clear(); //Clear fail status in case eof was set
    mInStream->seekg(static_cast<std::streamoff>(pos), std::ios::beg);
}
//-----------------------------------------------------------------------
size_t U2DataStreamFileStream::tell(void) const
{
    mInStream->clear(); //Clear fail status in case eof was set
    return (size_t)mInStream->tellg();
}
//-----------------------------------------------------------------------
bool U2DataStreamFileStream::eof(void) const
{
    return mInStream->eof();
}
//-----------------------------------------------------------------------
void U2DataStreamFileStream::close(void)
{
    if (mInStream)
    {
        // Unfortunately, there is no file-specific shared class hierarchy between fstream and ifstream (!!)
        if (mFStreamRO)
            mFStreamRO->close();
        if (mFStream)
        {
            mFStream->flush();
            mFStream->close();
        }

        if (mFreeOnClose)
        {
            // delete the stream too
            if (mFStreamRO)
                U2_DELETE_T(mFStreamRO, basic_ifstream, MEMCATEGORY_GENERAL);
            if (mFStream)
                U2_DELETE_T(mFStream, basic_fstream, MEMCATEGORY_GENERAL);
            mInStream = 0;
            mFStreamRO = 0; 
            mFStream = 0; 
        }
    }
}